/*
 * Copyright (C) 2007-2008  Hylke van der Schaaf
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, in version 3 of the License.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package hextd.core;

import com.usoog.tdcore.map.MapListener;
import com.usoog.tdcore.message.FactoryMessage;
import com.usoog.tdcore.message.Message;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.usoog.tdcore.map.MapInfo;
import com.usoog.tdcore.map.MapLoader;
import com.usoog.tdcore.message.MessageMapData;
import com.usoog.tdcore.message.MessageMapList;
import java.util.HashMap;

/**
 * MapLoaderServer loads maps from the webserver into the gameserver.
 * TODO: if the webserver is not available, it loads the backup-maps from the jar.
 * @author hylke
 */
public class MapLoaderServer implements MapLoader {

	protected Map<Integer, String> categories;
	protected Map<Integer, MapInfo> mapCache = new HashMap<Integer, MapInfo>();
	protected Map<String, MapInfo> mapCacheLocal = new HashMap<String, MapInfo>();
	protected List<MapInfo> mapList = null;
	protected List<MapInfo> mapListLocal = new ArrayList<MapInfo>();
	protected URL localMapContextUrl;
	private final URL mapListUrl, mapFetchUrl;
	private URL backupUrl;
	private MessageMapList mapListMessage;
	private final FactoryMessage messageFactory;

	public MapLoaderServer(FactoryMessage mf, URL mapListUrl, URL mapFetchUrl, String backupIndex, URL backupContextUrl) {
		this.messageFactory = mf;
		this.mapListUrl = mapListUrl;
		this.mapFetchUrl = mapFetchUrl;
		try {
			backupUrl = new URL(backupContextUrl, backupIndex);
			loadLocalIndex(backupUrl);
		} catch (MalformedURLException ex) {
			Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
		}
	}

	public MapInfo getMap(String fileName) {
		MapInfo info = mapCacheLocal.get(fileName);
		if (info != null && info.getContent() == null) {
			try {
				//System.out.println("HexTD::FetchFromUrl::file = " + fetchUrl.toString());
				BufferedReader bufferedreader;
				URL fetchUrl = new URL(localMapContextUrl, fileName);
				URLConnection urlconnection = fetchUrl.openConnection();
				if (urlconnection.getContentEncoding() != null) {
					bufferedreader = new BufferedReader(new InputStreamReader(urlconnection.getInputStream(), urlconnection.getContentEncoding()));
				} else {
					bufferedreader = new BufferedReader(new InputStreamReader(urlconnection.getInputStream(), "utf-8"));
				}
				String line;
				StringBuilder mapContent = new StringBuilder();
				while ((line = bufferedreader.readLine()) != null) {
					mapContent.append(line);
					mapContent.append("\n");
				}
				info.setContent(mapContent.toString());
			} catch (IOException _ex) {
				System.err.println("HexTD::readFile:: Can't read from " + fileName);
			}
		}
		return info;
	}

	/**
	 * Tries to load a local map and adds it to the map cache if successful.
	 * @param fileName
	 * @param contextDir
	 */
	protected void tryLoadLocalFile(String fileName, URL contextDir) {

		MapInfo levelInfo = new MapInfoImplementation();
		String[] split = fileName.split(" ");
		fileName = split[0];
		if (split[0].length() == 2 && split[0].charAt(0) == 'c') {
			int cat = Integer.parseInt(split[0].substring(1));
			if (categories == null) {
				categories = new HashMap<Integer, String>();
			}
			categories.put(cat, split[1]);
		} else {
			int cat = 0;
			int id = 0;
			if (split.length > 1) {
				id = Integer.parseInt(split[1]);
				cat = Integer.parseInt(split[2]);
			}
			if (split.length > 3) {
				int parseInt = Integer.parseInt(split[3]);
				if (parseInt > 0) {
					levelInfo.preReq.add(-parseInt);
				}
			}
			if (split.length > 4) {
				int parseInt = Integer.parseInt(split[4]);
				if (parseInt > 0) {
					levelInfo.nextMap = -parseInt;
				}
			}
			levelInfo.info.put("fileName", fileName);
			levelInfo.category = cat;

			levelInfo.mapId = -id;
			mapListLocal.add(levelInfo);
			mapCache.put(levelInfo.mapId, levelInfo);
			mapCacheLocal.put(fileName, levelInfo);

			// getMap will load the content of the map
			getMap(fileName);
			// getMapData will parse the content of the map and fill the mapInfo
			levelInfo.getMapData();

			System.out.println("ClientMapLoader:tryLoadFile: added " + levelInfo.players + " player map " + levelInfo.title + " to index.");
		}
	}

	public MessageMapList getMapListMessage() {
		if (mapListMessage == null) {
			fetchRemoteIndex();
		}
		return mapListMessage;
	}

	public void fetchRemoteIndex() {
		Message message;
		if (mapListUrl != null) {
			synchronized (mapListUrl) {
				try {
					URLConnection connection = mapListUrl.openConnection();
					connection.setDoOutput(false);
					InputStream is = connection.getInputStream();
					InputStreamReader isr = new InputStreamReader(is);
					BufferedReader in = new BufferedReader(isr);
					String receivedLine;
					while ((receivedLine = in.readLine()) != null) {
						message = messageFactory.parseString(receivedLine);
						if (message.getKey().equals(MessageMapList.key)) {
							mapListMessage = (MessageMapList) message;

							categories = mapListMessage.getCategories();
							Map<Integer, MapInfo> mapListTemp = mapListMessage.getMapCache();
							for (Entry<Integer, MapInfo> entry : mapListTemp.entrySet()) {
								Integer id = entry.getKey();
								MapInfo cached = mapCache.get(id);
								MapInfo info = entry.getValue();
								if (cached != null && cached.getContent() != null && cached.lastChange == info.lastChange) {
									info.setContent(cached.getContent());
								}
							}
							mapCache = mapListTemp;
						}
					}
					in.close();
					isr.close();
					is.close();
				} catch (IOException ex) {
					Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
				}
			}
			System.out.println("MapLoader::fetchIndex: got " + mapCache.size() + " maps.");
		}
	}

	private boolean fetchMap(MapInfo info) {
		Message message;
		if (mapFetchUrl != null) {
			try {
				URLConnection connection = mapFetchUrl.openConnection();
				connection.setDoOutput(true);
				connection.setConnectTimeout(30000);
				OutputStream os = connection.getOutputStream();
				OutputStreamWriter out = new OutputStreamWriter(os);
				out.write(URLEncoder.encode("mapId", "UTF-8") + "=" + URLEncoder.encode("" + info.mapId, "UTF-8") + "&");
				out.close();
				os.close();

				InputStream is = connection.getInputStream();
				InputStreamReader isr = new InputStreamReader(is);
				BufferedReader in = new BufferedReader(isr);

				String receivedLine;
				while ((receivedLine = in.readLine()) != null) {
					message = messageFactory.parseString(receivedLine);
					if (message.getKey().equals(MessageMapData.key)) {
						MessageMapData mapMessage = (MessageMapData) message;
						if (info.mapId == mapMessage.getMapId()) {
							info.setContent(mapMessage.getMapData());
							return true;
						}
					}
				}
				in.close();
				isr.close();
				is.close();
			} catch (IOException ex) {
				Logger.getLogger(MapLoaderServer.class.getName()).log(Level.SEVERE, null, ex);
			}
		}
		return false;
	}

	/**
	 * Loads the index of local map files.
	 * @param fileUrl the url of the index file for the local maps
	 * @param contextUrl the context to which the index url is relative.
	 */
	private void loadLocalIndex(URL indexUrl) {
		localMapContextUrl = indexUrl;
		List<String> levelNames;

		levelNames = MapLoaderClient.getLevelIndex(indexUrl);
		for (String levelName : levelNames) {
			tryLoadLocalFile(levelName, indexUrl);
		}

		mapListMessage = new MessageMapList();
		mapListMessage.setData(mapList, categories);
	}

	@Override
	public MapInfo getMap(int mapId) {
		MapInfo info = mapCache.get(mapId);
		if (info.getContent() == null) {
			fetchMap(info);
		}
		return info;
	}

	public List<MapInfo> getMapList() {
		throw new UnsupportedOperationException("Not supported yet.");
	}

	public void addMapListener(MapListener l) {
		throw new UnsupportedOperationException("Not supported yet.");
	}
}
